﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.CodeDom;
using System.Xml;
using System.Xml.Linq;
using Microsoft.VisualStudio.TextTemplating.VSHost;
using System.CodeDom.Compiler;
using System.IO;
using System.Runtime.InteropServices;
using System.ComponentModel; 

namespace RBTools.CodeGenerator.CodeDomGenerator.Message
{
    [GuidAttribute("7D575BC8-0CE1-4729-9021-567D98BBB4BB")]
    public class MessageCodeGenerator:BaseCodeGeneratorWithSite
    {
        #region Ctors

        public MessageCodeGenerator()
        {
            DefaultNamespace = "Messagess";
        }

        #endregion

        #region Properties

        public string DefaultNamespace
        {
            get;
            set;
        }

        #endregion

        #region Methods

        public CodeCompileUnit BuildCodeCompileUnit(XmlDocument xmlDoc)
        {
            EnvDTE.ProjectItem projectItem = GetService(typeof(EnvDTE.ProjectItem)) as EnvDTE.ProjectItem;
            if (projectItem != null)
            {
                DefaultNamespace = projectItem.ContainingProject.Name;
            }

            CodeCompileUnit codeCompileUnit = new CodeCompileUnit();
            
            CodeNamespace codeNamespace = new CodeNamespace(DefaultNamespace);

            codeNamespace.Imports.Add(new CodeNamespaceImport("System"));
            codeNamespace.Imports.Add(new CodeNamespaceImport("System.Text")); 

            codeCompileUnit.Namespaces.Add(codeNamespace);


            //Generate Message Class
            CodeTypeDeclaration messageTypeDeclaration = GenerateMessageEntryClass(xmlDoc);
            codeNamespace.Types.Add(messageTypeDeclaration);

            //Generate Messages Class
            CodeTypeDeclaration messagesTypeDeclaration = GenerateCategoryCalss(xmlDoc);
            codeNamespace.Types.Add(messagesTypeDeclaration);

            return codeCompileUnit;
        }

        private CodeTypeDeclaration GenerateMessageEntryClass(XmlDocument xmlDoc)
        {
            allPropertyNameMappings = new Dictionary<string, string[]>();

            var messageEntryElement = xmlDoc.GetElementsByTagName("messageEntry")[0];
            CodeTypeDeclaration messageEntryTypeDeclaration = new CodeTypeDeclaration(messageEntryElement.Attributes["className"].Value);

            CodeConstructor constructor = new CodeConstructor();
            constructor.Attributes = MemberAttributes.Public;
            constructor.Name = messageEntryElement.Attributes["className"].Value; 
            var propertyElements = messageEntryElement.FirstChild.ChildNodes.Cast<XmlElement>();
            foreach (var propertyElement in propertyElements)
            {
                string propertyName =propertyElement.Attributes["name"].Value;
                string propertyType =propertyElement.Attributes["type"].Value;
                string mappingTo = propertyElement.Attributes["mappingto"].Value;
                string fieldName = "_"+propertyName;
                string parameterName ="__"+propertyName;

                allPropertyNameMappings.Add(propertyName, new string[] { mappingTo, propertyType });

                CodeMemberField codeField = new CodeMemberField(propertyType,fieldName);
                messageEntryTypeDeclaration.Members.Add(codeField);

                CodeFieldReferenceExpression codeFieldReference = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);

                CodeMemberProperty codeProperty = new CodeMemberProperty();
                codeProperty.Name = propertyName;
                codeProperty.HasGet = true;
                codeProperty.HasSet = true;

                CodeMethodReturnStatement getPropertyReturn = new CodeMethodReturnStatement(codeFieldReference);
                codeProperty.GetStatements.Add(getPropertyReturn);
                CodeAssignStatement setPropertyAssign = new CodeAssignStatement(codeFieldReference,
                                    new CodePropertySetValueReferenceExpression());
                codeProperty.SetStatements.Add(setPropertyAssign);
                codeProperty.Type = new CodeTypeReference(propertyType);
                codeProperty.Attributes = MemberAttributes.Public;

                messageEntryTypeDeclaration.Members.Add(codeProperty);

                CodeAssignStatement codeAssign = new CodeAssignStatement();
                codeAssign.Left = codeFieldReference;
                codeAssign.Right = new CodeArgumentReferenceExpression(parameterName);
                constructor.Statements.Add(codeAssign);

                constructor.Parameters.Add(new CodeParameterDeclarationExpression(propertyType, parameterName));
            }

            messageEntryTypeDeclaration.Members.Add(constructor);

            return messageEntryTypeDeclaration;
        }

        private CodeTypeDeclaration GenerateCategoryCalss(XmlDocument xmlDoc)
        {
            CodeTypeDeclaration messagesTypeDeclaration = new CodeTypeDeclaration("Messages");

            var messageEntryClassName = xmlDoc.GetElementsByTagName("messageEntry")[0].Attributes["className"].Value;
            var messageElements = xmlDoc.GetElementsByTagName("message").Cast<XmlElement>();
            var categories = (from e in messageElements select e.Attributes["category"].Value).Distinct();
            foreach (var category in categories)
            {
                var categoryTypeDeclaration = new CodeTypeDeclaration(category);
                messagesTypeDeclaration.Members.Add(categoryTypeDeclaration);

                var messagesInTheCategory = messageElements.Where(e => e.GetAttribute("category") == category);
                foreach (var element in messagesInTheCategory)
                {
                    GenerateCategoryProperties(categoryTypeDeclaration, element,messageEntryClassName);
                }
            }
            return messagesTypeDeclaration;
        }

        private void GenerateCategoryProperties(CodeTypeDeclaration categoryTypeDeclaration, XmlElement messageElement,string messageEntryClassName)
        { 
            string id = messageElement.GetAttribute("id"); 

            var categoryField = new CodeMemberField(messageEntryClassName, id);
            categoryField.Attributes = MemberAttributes.Static | MemberAttributes.Public;

            CodeObjectCreateExpression codeObjectCreateExp = new CodeObjectCreateExpression(messageEntryClassName);
            foreach (var kv in allPropertyNameMappings)
            {
                string propertyValueStr = messageElement.GetAttribute(allPropertyNameMappings[kv.Key][0]);
                Type propertyType = Type.GetType(allPropertyNameMappings[kv.Key][1]);
                TypeConverter converter = TypeDescriptor.GetConverter(propertyType);
                object propertyValue =  converter.ConvertFromString(propertyValueStr); 
                codeObjectCreateExp.Parameters.Add(new CodePrimitiveExpression(propertyValue));
            }
            categoryField.InitExpression = codeObjectCreateExp;
            categoryTypeDeclaration.Members.Add(categoryField);
        }

        #endregion

        #region Implement BaseCodeGeneratorWithSite

        protected override byte[] GenerateCode(string inputFileName, string inputFileContent)
        {
            var xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(inputFileContent);

            var codeCompileUnit = BuildCodeCompileUnit(xmlDoc);
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BracingStyle = "C";
            using (StringWriter sWriter = new StringWriter())
            {
                provider.GenerateCodeFromCompileUnit(codeCompileUnit, sWriter, options);
                var code = sWriter.ToString();
                byte[] preambleBytes = Encoding.Unicode.GetPreamble();
                byte[] codeBytes = Encoding.Unicode.GetBytes(code);
                byte[] result = new byte[preambleBytes.Length + codeBytes.Length];
                Buffer.BlockCopy(preambleBytes, 0, result, 0, preambleBytes.Length);
                Buffer.BlockCopy(codeBytes, 0, result, preambleBytes.Length, codeBytes.Length);
                return result;
            }
        }

        public override string GetDefaultExtension()
        {
            return ".cs";
        }

        #endregion

        #region Other Methods

        private Dictionary<string, string[]> allPropertyNameMappings = new Dictionary<string, string[]>();

        #endregion
    }
}
